
/* GCSx
** COMPILE.H
**
** Bytecode compilation (to pre-link state)
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_COMPILE_H_
#define __GCSx_COMPILE_H_

class Compiler {
private:
    // Only valid after entire compile process
    int errorCount;
    int warningCount;

    struct Parameter {
        DataType type;
        std::string name;
    };
    
    const std::list<std::string>* source;
    const World* world;
    int scriptType;
    class Tokenizer* t;
    FunctionMap* func;
    VariableMap* vars;
    LabelMap* labels;
    int scope;
    // current function
    int inFunctionScope;
    int funcEntryStackDepth;
    int funcParamStackDepth;
    
    // Offsets of all existing strings, to prevent duplication
    typedef hash_map<const char*, int, hash<const char*>, hash_eqstr> StringMap;
    StringMap stringMap;
    
    struct Codespace {
        std::vector<Uint32> code;
        // Add to all var accesses on stack
        // Should (must) be 0 when merging before an existing codeblock
        int stackDepth;
        // Linker entries
        std::list<LinkEntry> links;
    };

    Codespace main; // Main code outside of functions
    Codespace localInit; // Local var initialization (pre-main-code)
    Codespace functions; // All functions
    Codespace strings; // All static strings
    std::vector<Codespace*> workspaces; // Points to where we're writing code
    Codespace* ws; // Optimized reference to workspace.back()
    
    void pushWorkspace(Codespace* newWorkspace) { workspaces.push_back(ws = newWorkspace); }
    void popWorkspace() { workspaces.pop_back(); ws = workspaces.back(); }
    // Append a codespace to end of another, merges links as well
    // Doesn't modify src but you should probably delete it afterwards
    void appendWorkspaces(Codespace* dest, const Codespace* src);
    // Removes and finishes string/function links, copies others to final link table and adds offset
    void completeLinkage(std::list<LinkEntry>* links, Codespace& code, int linkOffset, int strOffset, int funcOffset);
    
    void doParseSymbols();
    void doCompile(Uint32** bytecode, std::list<LinkEntry>* links);
    // (returns various ended values, used by parent blocks)
    struct Ended {
        int how;
        int scope;
        int breakInfinite;
    };
    // returnType may be present without parameters, for subscopes of functions
    void compileBlock(Ended& ended, const std::vector<Parameter>* parameters = NULL, DataType* returnType = NULL);
    
    static int isDataType(int tokenType);
    DataType parseDataType();
    void checkDataType(DataType& type, int noConst, int noScope, int noVisibility, int noVoid);
    
    // Returns true if successful enough to use; either way, uses up tokens
    // Returns -1 if successful, but no tokens used (ie, blank param list no parens)
    // May use as little as no tokens if no datatype or paren present (does not use brace)
    int parseParameterList(std::vector<Parameter>& list);
    
    // All cmds that post code assume they're working on stack top unless noted
    // i.e. temporaries are on stack top, results will be on stack top
    
    struct Operand {
        // The operand mode
        // ALL flags/types allowed except-
        //   OM_COPY filled in at last moment
        //   OM_POP filled in at last moment
        //   Strings convert to OM_STR_CONST at last moment (even literals)
        // 0 typically results no operand or ERROR condition
        OperMode mode;
        int arrayType; // (or hash type) A OM_BASETYPE constant, or OM_ENTRY
        
        // The literal data written to the bytecode
        // operand data is NOT tracked accurately for temporaries on stack (O_IS_TEMP)
        union {
            Sint32 i; // Used for offsets, obj types, etc. also
            BCfloat f;
            void* p;
        } data;
        int subType;

        // Flags
        enum {
            // is a variable- don't modify directly or pop (used with OM_STACK)
            OF_IS_VAR = 1,
            // is gauranteed to not reference another array/hash (only used when O_IS_TEMP)
            OF_IS_COPY = 2,
            // used with a constant INT value to prevent mode looking like an error
            OF_IS_VOID = 4,
            // is a function return value or assignment result or other result that had side-effects
            OF_IS_SIDEFX = 8,
        };
        int flags;

        // Reference to source of data
        union {
            // for global vars ONLY, pointer to original variable for creating a linker ref
            const Variable* global;
            // for string literals ONLY; not turned into data until used
            // if NULL, data is already correct
            std::string* strLiteral;
        } ref;
    };
    
    static void createEmpty(Operand& o);
    static void createInt(Operand& o, Sint32 value);
    static void createFloat(Operand& o, BCfloat value);
    // You must add a local/global/stack, and any flags; always adds OM_NO_CONVERT
    static Operand dataTypeToOperand(DataType dt);

    // Easy access to flags
    #define O_IS_VAR(x)     ((x).flags & Operand::OF_IS_VAR)
    #define O_IS_COPY(x)    ((x).flags & Operand::OF_IS_COPY)
    #define O_IS_VOID(x)    ((x).flags & Operand::OF_IS_VOID)
    #define O_IS_SIDEFX(x)  ((x).flags & Operand::OF_IS_SIDEFX)
    // Is an operand a "temporary"?
    #define O_IS_TEMP(x)    (OM_IS_STACK((x).mode) && !O_IS_VAR(x))
    
    // Parse any valid expression, posts code to get to result
    // warnAssign should start at 2 if you want to allow 1 set of parenthesis and still warn
    // These never return operand modes listed as "last minute"; 0 = error; all other modes possible
    // "usingValue" is basically "don't want a void result"
    Operand parseExpression(int warnAssign = 0, int usingValue = 1, Operand* firstOperand = NULL, int stopPrecedence = 100, const Variable* varToCount = NULL);
    Operand parseOperand(int warnAssign = 0, int usingValue = 1, const Variable* varToCount = NULL, Operand* memberOf = NULL);
    Operand variableToOperand(const Variable& var);
    // (opertoken is just there to assist with error msgs)
    // uses up both operands and produces another
    Operand parseOperation(int oper, const std::string& operToken, Operand& a, Operand& b);
    // Assumes you've moved past the =, posts code to get result
    void parseAssignment(Operand& var, DataType varType, const Variable* varToCount = NULL, int memberPos = -1);
    // Function call- Parses any parameters for you; returns o.mode=0 if void return value
    Operand parseFunction(const Function& function, const std::string& funcName);
    // Returns true if found
    int findFunction(const std::string& name, Function& function) const;
    // (uses up operand)
    void processReturn(Operand& o, DataType* returnType);

    // Convert various operand modes, posting code if needed and converting operand itself
    // Assumes and produces no "last minute" modes or 0.
    // Handles ANY other type of conversion- makes no assumptions- uses FORCE* if needed
    void convertToInt(Operand& o);
    void convertToFloat(Operand& o);
    void convertToStr(Operand& o);
    void convertToArray(Operand& o, DataType datatype);
    void convertToHash(Operand& o, DataType datatype);
    // Handles subtype if present
    void convertToEntity(Operand& o, DataType datatype);
    // Handles subtype if present
    void convertToObject(Operand& o, DataType datatype);
    // Displays errors if conversion is not a "naturally" allowed conversion
    void convertToMatch(Operand& o, DataType datatype, const char* errorTarget);
    
    // Converts something to a temporary (O_IS_TEMP)
    // Gauranteed to be ref-free from original version
    // partialConvert is only intended for literals-
    //  -skips defining literal string 
    //  -leaves o.data
    // This allows undoing or "completing" the conversion later.
    // Returns true if string needs to be converted later
    int convertToTemp(Operand& o, int partialConvert = 0, int refOK = 0);
    
    // Frees any string, pops if temporary, asserts on top of stack and not OM_POP
    // Safe to call with o.mode = 0
    void destroyOperand(Operand& o);
    
    // Pushes an appropriate empty value on the stack (doesn't modify stackdepth)
    void generateBlank(DataType& type);
    
    // (these use no tokens)
    const Variable& defineVariable(DataType type, const std::string& name, int isParam = 0);
    int defineString(const std::string& str); // Returns offset within strings
    void defineString(Operand& o); // Converts a static string to a ready-to-use operand
    static int precedence(int oper);
    
    // Call before using a postCmd* function to post appropriate linker entries
    // Link to local function; assumes first oper
    void prepLinker(const std::string& funcName);
    // Link to loop continue or break; assumes first oper
    void prepLinker(int isContinue, const std::string& loopName);

    // Link to local string at current position- called from within postCmdRaw
    void prepLinker();
    // Link to global var or it's data- called from within postCmdRaw
    void prepLinker(const Variable* var, int toData = 1);

    // All possible variations of opcode and operands; returns total int32s used
    int postCmdRaw(Opcode opc, Operand* o1 = NULL, Operand* o2 = NULL, Operand* o3 = NULL);
    // Quick/convenient versions
    void postCmd(Opcode opc);
    void postCmdI(Opcode opc, Sint32 int1);
    void postCmdII(Opcode opc, Sint32 int1, Sint32 int2);
    void postCmdF(Opcode opc, BCfloat float1);
    void postCmdS(Opcode opc, Sint32 str1);
    // (doesn't define string for you- just uses data.i)
    void postCmdPush(Operand& oper, int refOK = 0);
    // Offset is from current end-of-code and will be adjusted
    // Returns position of offset in bytecode in case you want to adjust further
    // (original offset is adjusted based on <0 or >=0, so at least get that right)
    // Returns -1 if no offset (if condition was always true/false this can occur)
    // Tells you if it always/never jumps using last two parameters
    int postCmdIf(int ifTrue, Sint32 offset, Operand& oper, int& alwaysJumps, int& neverJumps);

public:
    Compiler(const std::list<std::string>* src, const World* srcWorld);
    ~Compiler();
    
    // Just parse symbols
    // Returns true if fatal errors and deletes entire map
    int parseSymbols(FunctionMap* funcMap);
    
    // Do a full compile
    // Assumes symbols have been parsed, above
    // Returns true if errors
    // Code may or may not be allocated in error situation
    int compile(Uint32** bytecode, FunctionMap* funcMap, std::list<LinkEntry>* links, int scriptId);

    int numErrors() { return errorCount; }
    int numWarnings() { return warningCount; }
};

#endif
